package storage

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"time"

	"gitee.com/konglinglong/car/backend/internal/config"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

// Client represents a storage client interface
type Client interface {
	PutObject(ctx context.Context, objectName string, reader io.Reader, objectSize int64, opts PutObjectOptions) error
	GetObject(ctx context.Context, objectName string) (io.ReadCloser, error)
	DeleteObject(ctx context.Context, objectName string) error
	ObjectExists(ctx context.Context, objectName string) (bool, error)
	GetObjectInfo(ctx context.Context, objectName string) (ObjectInfo, error)
}

// PutObjectOptions represents options for putting an object
type PutObjectOptions struct {
	ContentType string
	Metadata    map[string]string
}

// ObjectInfo represents object information
type ObjectInfo struct {
	Key          string
	Size         int64
	ContentType  string
	LastModified string
	ETag         string
}

// MinioClient implements the Client interface using MinIO
type MinioClient struct {
	client *minio.Client
	bucket string
}

// LocalClient implements the Client interface using local filesystem
type LocalClient struct {
	basePath string
}

// Initialize initializes the storage client
func Initialize(cfg config.StorageConfig) (Client, error) {
	switch cfg.Type {
	case "minio", "s3":
		return initializeMinioClient(cfg)
	case "local":
		return initializeLocalClient(cfg)
	default:
		return nil, fmt.Errorf("unsupported storage type: %s", cfg.Type)
	}
}

// initializeMinioClient initializes a MinIO client
func initializeMinioClient(cfg config.StorageConfig) (*MinioClient, error) {
	// Initialize MinIO client
	client, err := minio.New(cfg.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
		Secure: cfg.UseSSL,
		Region: cfg.Region,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create MinIO client: %w", err)
	}

	// Create bucket if it doesn't exist
	ctx := context.Background()
	exists, err := client.BucketExists(ctx, cfg.Bucket)
	if err != nil {
		return nil, fmt.Errorf("failed to check bucket existence: %w", err)
	}

	if !exists {
		err = client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{
			Region: cfg.Region,
		})
		if err != nil {
			return nil, fmt.Errorf("failed to create bucket: %w", err)
		}
		log.Printf("Created bucket: %s", cfg.Bucket)
	}

	log.Printf("Storage initialized successfully (type: %s, bucket: %s)", cfg.Type, cfg.Bucket)
	return &MinioClient{
		client: client,
		bucket: cfg.Bucket,
	}, nil
}

// PutObject uploads an object to storage
func (mc *MinioClient) PutObject(ctx context.Context, objectName string, reader io.Reader, objectSize int64, opts PutObjectOptions) error {
	minioOpts := minio.PutObjectOptions{
		ContentType: opts.ContentType,
		UserMetadata: opts.Metadata,
	}

	_, err := mc.client.PutObject(ctx, mc.bucket, objectName, reader, objectSize, minioOpts)
	if err != nil {
		return fmt.Errorf("failed to put object %s: %w", objectName, err)
	}

	return nil
}

// GetObject retrieves an object from storage
func (mc *MinioClient) GetObject(ctx context.Context, objectName string) (io.ReadCloser, error) {
	object, err := mc.client.GetObject(ctx, mc.bucket, objectName, minio.GetObjectOptions{})
	if err != nil {
		return nil, fmt.Errorf("failed to get object %s: %w", objectName, err)
	}

	return object, nil
}

// DeleteObject deletes an object from storage
func (mc *MinioClient) DeleteObject(ctx context.Context, objectName string) error {
	err := mc.client.RemoveObject(ctx, mc.bucket, objectName, minio.RemoveObjectOptions{})
	if err != nil {
		return fmt.Errorf("failed to delete object %s: %w", objectName, err)
	}

	return nil
}

// ObjectExists checks if an object exists in storage
func (mc *MinioClient) ObjectExists(ctx context.Context, objectName string) (bool, error) {
	_, err := mc.client.StatObject(ctx, mc.bucket, objectName, minio.StatObjectOptions{})
	if err != nil {
		// Check if error is "object not found"
		if minio.ToErrorResponse(err).Code == "NoSuchKey" {
			return false, nil
		}
		return false, fmt.Errorf("failed to check object existence %s: %w", objectName, err)
	}

	return true, nil
}

// GetObjectInfo retrieves object information
func (mc *MinioClient) GetObjectInfo(ctx context.Context, objectName string) (ObjectInfo, error) {
	stat, err := mc.client.StatObject(ctx, mc.bucket, objectName, minio.StatObjectOptions{})
	if err != nil {
		return ObjectInfo{}, fmt.Errorf("failed to get object info %s: %w", objectName, err)
	}

	return ObjectInfo{
		Key:          objectName,
		Size:         stat.Size,
		ContentType:  stat.ContentType,
		LastModified: stat.LastModified.Format(time.RFC3339),
		ETag:         stat.ETag,
	}, nil
}

// initializeLocalClient initializes a local filesystem client
func initializeLocalClient(cfg config.StorageConfig) (*LocalClient, error) {
	basePath := cfg.Endpoint
	if basePath == "" {
		basePath = "./storage"
	}
	
	// Create base directory if it doesn't exist
	if err := os.MkdirAll(basePath, 0755); err != nil {
		return nil, fmt.Errorf("failed to create storage directory: %w", err)
	}
	
	return &LocalClient{
		basePath: basePath,
	}, nil
}

// PutObject stores an object in the local filesystem
func (lc *LocalClient) PutObject(ctx context.Context, objectName string, reader io.Reader, objectSize int64, opts PutObjectOptions) error {
	filePath := filepath.Join(lc.basePath, objectName)
	
	// Create directory if it doesn't exist
	dir := filepath.Dir(filePath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}
	
	// Create and write file
	file, err := os.Create(filePath)
	if err != nil {
		return fmt.Errorf("failed to create file: %w", err)
	}
	defer file.Close()
	
	_, err = io.Copy(file, reader)
	if err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}
	
	return nil
}

// GetObject retrieves an object from the local filesystem
func (lc *LocalClient) GetObject(ctx context.Context, objectName string) (io.ReadCloser, error) {
	filePath := filepath.Join(lc.basePath, objectName)
	file, err := os.Open(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open file: %w", err)
	}
	return file, nil
}

// DeleteObject removes an object from the local filesystem
func (lc *LocalClient) DeleteObject(ctx context.Context, objectName string) error {
	filePath := filepath.Join(lc.basePath, objectName)
	err := os.Remove(filePath)
	if err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to delete file: %w", err)
	}
	return nil
}

// ObjectExists checks if an object exists in the local filesystem
func (lc *LocalClient) ObjectExists(ctx context.Context, objectName string) (bool, error) {
	filePath := filepath.Join(lc.basePath, objectName)
	_, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, fmt.Errorf("failed to check file existence: %w", err)
	}
	return true, nil
}

// GetObjectInfo retrieves object information from the local filesystem
func (lc *LocalClient) GetObjectInfo(ctx context.Context, objectName string) (ObjectInfo, error) {
	filePath := filepath.Join(lc.basePath, objectName)
	stat, err := os.Stat(filePath)
	if err != nil {
		return ObjectInfo{}, fmt.Errorf("failed to get file info: %w", err)
	}
	
	return ObjectInfo{
		Key:          objectName,
		Size:         stat.Size(),
		ContentType:  "application/octet-stream",
		LastModified: stat.ModTime().Format(time.RFC3339),
		ETag:         "",
	}, nil
}